// tip simple style tooltips for jquery
// version 1.0.0a
// (c) 2008-2010 jason frame [jason@onehackoranother.com]
// releated under the MIT license

(function($) {

    function data(element, value) {
        // unwrap jquery if an instance of jquery is passed
        element = element.tip != null ? element[0] : element;
        
        if (value) {
            $.data(element, 'tip', value);
        }
        return $.data(element, 'tip');
    }

    $.fn.tip = function(opts) {
        if (opts === true) {
            return data(this);
        } else if (typeof opts == 'string') {
            var args = Array.prototype.slice.call(arguments);
            args.shift();
            return $.fn.tip.methods[opts].apply(this, args);
        }
        
        opts = $.extend({}, $.fn.tip.defaults, opts);

        return this.each(function() {
            // increase unique tip count
            $.fn.tip.count = ($.fn.tip.count || 1) + 1;

            var element = $(this);
            
            var tip = "tip-" + $.fn.tip.count;
            var tipElement = $('<div class="tip"></div>')
                            .html('<div class="tip-arrow"></div><div class="tip-inner"/></div>')
                            .attr("id", tip)
                            .css({display: "none"});

            var tipInner = $(tipElement).find(".tip-inner");
            
            // create a unique copy of the options to store tip id
            var options = $.extend(
                    {tip: "#" + tip},
                    $.fn.tip.elementOptions(element[0], opts));
            
            if (options.container) {
                tipInner.empty().append($(options.container).show().detach());
            } else {
                var title = element.attr($.fn.tip.defaults.title) || options.title;
                if (title) {
                    if ($.isFunction(title)) {
                        title = title.call(element);
                    }
                    
                    title.replace(/(^\s*|\s*$)/);
                    tipInner[options.html ? 'html' : 'text'](title);
                } else {
                    // no title, no displayable content get out of here
                    return;
                }
            }

            // store individual data with tip id
            data(element[0], options);
            
            tipElement.appendTo("body");
            
            if (options.trigger != 'manual') {
                if (options.trigger == 'click') {
                    element.bind('click', $.fn.tip.methods.toggle);
                } else {
                    var eventIn  = options.trigger == 'hover' ? 'mouseenter' : 'focus',
                        eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
                    element.bind(eventIn, $.fn.tip.methods.show)
                           .bind(eventOut, $.fn.tip.methods.hide)
                           .trigger("tip.afterInitialize", [tipElement]);
                }
            }

            element.addClass("tip-enabled");
        });
    };
    
    $.fn.tip.defaults = {
        delayIn: 0,
        delayOut: 0,
        fade: false,
        fallback: '',
        gravity: 'n',
        html: false,
        offset: 0,
        opacity: 0.8,
        title: 'title',
        trigger: 'hover'
    };

    $.fn.tip.methods = {
        show: function() {
            var element = $(this);
            var options = data(element);
            
            var tip = $(options.tip);
            
            if (element.is(".tip-enabled:not(.tip-active)")) {
                element.trigger("tip.beforeShow", [tip]);

                tip.attr("class", "tip") // reset classname in case of dynamic gravity
                   .css({top: 0, left: 0, display: 'none'});

                var pos = $.extend({}, element.offset(), {
                    width: element[0].offsetWidth,
                    height: element[0].offsetHeight
                });

                var actualWidth = tip[0].offsetWidth, actualHeight = tip[0].offsetHeight;
                var gravity = (typeof options.gravity == 'function')
                                ? options.gravity.call(element[0])
                                : options.gravity;

                var tp;
                switch (gravity.charAt(0)) {
                    case 'n':
                        tp = {top: pos.top + pos.height + options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
                        break;
                    case 's':
                        tp = {top: pos.top - actualHeight - options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
                        break;
                    case 'e':
                        tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - options.offset};
                        break;
                    case 'w':
                        tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + options.offset};
                        break;
                }

                if (gravity.length == 2) {
                    if (gravity.charAt(1) == 'w') {
                        tp.left = pos.left + pos.width / 2 - 15;
                    } else {
                        tp.left = pos.left + pos.width / 2 - actualWidth + 15;
                    }
                }

                tip.css(tp).addClass('tip-' + gravity);

                if (options.fade) {
                    tip.stop()
                       .css({opacity: 0, display: 'block'})
                       .animate({opacity: options.opacity});
                } else {
                    tip.css({opacity: options.opacity, display: 'block'});
                }

                element.addClass("tip-active")
                       .trigger("tip.afterShow", [tip]);
            }
        },

        hide: function() {
            var options = data(this);
            var element = $(this);
            
            var tip = $(options.tip);

            if (element.is(".tip-active")) {
                element.trigger("tip.beforeHide", [tip]);

                if (options.fade) {
                    tip.stop().fadeOut(function() {
                        element.trigger("tip.afterHide", [tip]);
                    });
                } else {
                    tip.hide();
                    element.trigger("tip.afterHide", [tip])
                           .removeClass("tip-active");
                }
            }
        },

        toggle: function() {
            var element = $(this);

            if (element.is(".tip-active")) {
                element.tip("hide");
            } else {
                element.tip("show");
            }
        },
        
        enable: function() {
            $(this).addClass("tip-enabled");
        },
        disable: function() {
            $(this).removeClass("tip-enabled");
        },
        toggleEnabled: function() {
            $(this).toggleClass("tip-enabled");
        }
    };

    // Overwrite this method to provide options on a per-element basis.
    // For example, you could store the gravity in a 'tip-gravity' attribute:
    // return $.extend({}, options, {gravity: $(ele).attr('tip-gravity') || 'n' });
    // (remember - do not modify 'options' in place!)
    $.fn.tip.elementOptions = function(ele, options) {
        return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
    };

    $.fn.tip.autoNS = function() {
        return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
    };

    $.fn.tip.autoWE = function() {
        return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
    };

})(jQuery);